-- 猎人技能计算公式
--  damage = (武器面板伤害+(弹药dps+攻强/14)*2.8+技能固定增伤+瞄准镜)*1.05射击天赋，强化多重和8T1在此基础上*1.15*1.15

if not HST then return end

HST.DefaultAction = {
	["AutoShot"] = 23,
	["TrueShot"] = 22,
	["MultiShot"] = 21
}

HST.StrategyThreshold = 0.4
HST.FastShootSpeed = 2.2
HST.CurDmgCycle = nil
HST.BeingCasted = nil

local Threshold = HST.StrategyThreshold
local HunterCasts = {
	["TrueShot"] = "TrueShot",
	["MultiShot"] = "MultiShot",
	["Sting"] = "Sting"
}

-- A damage cycle is a set of casts between two autoshots.
-- By design, one damage group starts by an autoshot and
-- consists of one spell group as below:
-- 1. A single trueshot
-- 2. A single multishot
-- 3. Two successive trueshots
-- 4. A single trueshot and a multishot

-- Also, one damage group may not have a spell.

-- Casts of a damage cycle may delay the next autoshot, thus
-- delaying the start time of the next damage cycle.

DamageCycle = {
	-- Whether the current cast will block the next presumed autoshot
	WillBlockNextShot = function(CastTime)
		if not HST.CurDmgCycle then return false end
		local now, next = GetTime(), HST.CurDmgCycle.GroupEnd
		return now < next and next < now + CastTime
	end,
	SwingHasStarted = function()
		if not HST.CurDmgCycle then return false end
		return GetTime() >= HST.CurDmgCycle.SwingStart
	end
}

function DamageCycle:Create()
	obj = {}
	setmetatable(obj, self)
	obj._index = self
	obj.GroupStart = HST.lastshot
	local rangeSpeed = UnitRangedDamage("player")
	obj.GroupEnd = HST.lastshot + rangeSpeed
	obj.SwingStart = obj.GroupEnd - HST:GetAutoShotCastTime()
	obj.TrueShotCast = nil
	obj.MultiShotCast = nil
	obj.BlockNext = DamageCycle.WillBlockNextShot
	obj.SwingHasStarted = DamageCycle.SwingHasStarted
	obj.EnoughTimeBeforesSwing = function()
		if obj.SwingHasStarted() then 
			return false
		end

		local now = GetTime()
		return now < obj.GroupEnd and 
			now + HST.StrategyThreshold < obj.SwingStart
	end
	obj.IsObsolete = function()
		return GetTime() > obj.GroupEnd
	end
	return obj
end

function HST:ApiOnEvent(event, arg1)
	if event == "ITEM_LOCK_CHANGED" then 
		self:ApiHandleItemLockChanged()
	elseif event == "UNIT_RANGEDDAMAGE" then 
		self:ApiHandleRangeDamageChanged()
	elseif event == "SPELLCAST_INTERRUPTED" then 
		self:ApiHandleSpellCastFail()
	elseif event == "SPELLCAST_FAILED" then 
		self:ApiHandleSpellCastFail()
	elseif event == "SPELLCAST_STOP" then 
		self:ApiHandleSpellCastStop()
	elseif event == "STOP_AUTOREPEAT_SPELL" then 
		self:ApiHandleAutoRepeatStop()
	end
end

-- For auto shots
function HST:ApiHandleItemLockChanged()
	HST.CurDmgCycle = DamageCycle:Create()
end

function HST:ApiHandleRangeDamageChanged()
	if self.CurDmgCycle then
		local rangeSpeed = UnitRangedDamage("player")
		self.CurDmgCycle.GroupEnd = self.CurDmgCycle.GroupStart + rangeSpeed
		self.CurDmgCycle.SwingStart = self.CurDmgCycle.GroupEnd - HST:GetAutoShotCastTime()
	end
end

function HST:ApiHandleSpellCastStop()
	self:ProcessCastResult(1)
end

function HST:ApiHandleSpellCastFail()
	self:ProcessCastResult(nil)
end

function HST:ApiHandleAutoRepeatStop()
	self.CurDmgCycle = nil
end

function HST:ProcessCastResult(Success)
	if not self.CurDmgCycle then return end

	if self.BeingCasted == HunterCasts["TrueShot"] then
		self.CurDmgCycle.TrueShotCast = Success
	elseif self.BeingCasted == HunterCasts["MultiShot"] then
		self.CurDmgCycle.MultiShot = Success
	end
end

function HST:PlayerMoving()
	return self.moving
end

-------------------------  External API  --------------------------------------
function HST:StartCastLoop(AutoShotFirst, TrueShotSlotId, CastMultiShot, MultiShotSlotId)
	if CastMultiShot and MultiShotSlotId and 
		self:MultiShotActionAllowed(MultiShotSlotId, AutoShotFirst) then
		self:DamageCycleUseAction(MultiShotSlotId, HunterCasts["MultiShot"])
	elseif self:TrueShotActionAllowed(TrueShotSlotId, AutoShotFirst) then
		self:DamageCycleUseAction(TrueShotSlotId, HunterCasts["TrueShot"])
	end
end

function HST:MultiShotActionAllowed(Slot, AutoFirst)
	return self:ActionAllowed(Slot, self:GetMultiShotCastTime(), AutoFirst)
end

function HST:TrueShotActionAllowed(Slot, AutoFirst)
	return self:ActionAllowed(Slot, self:GetSteadyShotCastTime(), AutoFirst)
end

-- HunterCast MUST comes from HunterCasts table
function HST:DamageCycleUseAction(Slot, HunterCast)
	self:UseAction(Slot)
	self.BeingCasted = HunterCast
end

function HST:ActionAllowed(SlotId, CastTime, AutoFirst)
	if not HasAction(SlotId) or 
		not IsActionInRange(SlotId) or 
		GetActionCooldown(SlotId) > 0 or
		IsCurrentAction(SlotId) or
		self:PlayerMoving() or 
		self:CheckGCD() then 
			return false
	end

	if AutoFirst == nil then AutoFirst = true end
	if AutoFirst then
		return not (not HST.CurDmgCycle or 
			HST.CurDmgCycle.SwingHasStarted() or 
			HST.CurDmgCycle.BlockNext())
	else
		-- Autoshot repeat has just started.
		-- If autoshot is not compulsory, any spell is allowd
		-- when the autoshot hasn't started. 
		if not HST.CurDmgCycle then
			HST.CurDmgCycle = DamageCycle:Create()
			HST.CurDmgCycle.SwingStart = GetTime()
			HST.CurDmgCycle.GroupEnd = 
				HST.CurDmgCycle.SwingStart + HST:GetAutoShotCastTime()
			return true
		end

		-- Multishot is the highest spell to cast, and
		-- it may only slightly block the next autoshot while
		-- causing considerable amount of damage
		if CastTime <= 0.5 then return true end

		-- In normal cases, no spells are allowed when the 
		-- swing has started.
		-- But there are exceptions when current damage group marks
		-- the swing start but actually the autoshot hasn't started
		-- casting yet. It may happen for the first damage cycle which
		-- is started by a spell.
		if HST.CurDmgCycle.SwingHasStarted() then 
			return not IsAutoRepeatAction(HST.DefaultAction["AutoShot"])
		end

		-- Otherwise, spells are allowed if there's enough 
		-- time before the swing starts.
		return HST.CurDmgCycle.EnoughTimeBeforesSwing()
	end
end

--------------------  External Slash Commands Calls  ---------------------

-- Simple call of commonly-used casting cycle without multishot
HST.StartCastNoMultiShot = function()
	HST:StartCastLoop(false, HST.DefaultAction["TrueShot"])
end

-- Simple call of commonly-used casting cycle with multishot
HST.StartCastMultiShot = function()
	HST:StartCastLoop(false, HST.DefaultAction["TrueShot"], true, HST.DefaultAction["MultiShot"])
end